﻿// Arrow calculation taken from ArrowLineBase.cs (c) 2007 by Charles Petzold

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Win32;
using System.Windows.Controls;
using System.Windows;
using System.Windows.Media;
using System.Windows.Shapes;
using System.Globalization;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using UmlControlLibrary;
using UmlControlLibrary;

namespace UmlControlLibrary
{
    public class GraphViz
    {
        public static string CallGraphViz(string s)
        {
            RegistryKey hklm = Registry.LocalMachine;
            hklm = hklm.OpenSubKey("SOFTWARE\\ATT\\Graphviz");
            string installPath = (string)hklm.GetValue("InstallPath");

            System.Diagnostics.Process proc = new System.Diagnostics.Process();
            proc.StartInfo.FileName = installPath + "\\bin\\dot.exe";
            proc.StartInfo.Arguments = "-Tplain-ext";
            proc.StartInfo.UseShellExecute = false;
            proc.StartInfo.RedirectStandardOutput = true;
            proc.StartInfo.RedirectStandardInput = true;
            proc.StartInfo.CreateNoWindow = true;
            proc.StartInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
            proc.Start();
            proc.StandardInput.Write(s);
            proc.StandardInput.Flush();
            proc.StandardInput.Close();
            s = proc.StandardOutput.ReadToEnd();
            proc.WaitForExit();

            return s;
        }

        public static string GetGraphVizString(UmlControl control)
        {
            double x,y,w,h;
            UmlClass umlClass = control.DataContext as UmlClass;
            NumberFormatInfo n = new NumberFormatInfo();
            n.NumberDecimalSeparator = ".";
            Canvas rootCanvas = (control.Parent as Canvas);
            x = Canvas.GetLeft(control) / 100;
            y = Canvas.GetTop(control) / 100;
            w = control.ActualWidth / 100;
            h = control.ActualHeight / 100;
            x += w/2;
            y += h/2;
            y = (rootCanvas.ActualHeight - y);

            StringBuilder sb = new StringBuilder();
            sb.Append(control.GetHashCode().ToString());
            sb.AppendLine(" [ ");
            sb.AppendLine("fixedsize=true");
            sb.AppendLine("width=\"" + w.ToString(n) + "\"");
            sb.AppendLine("height=\"" + h.ToString(n) + "\"");

        /*    if (control.Locked)
                sb.AppendLine("pos=" + x.ToString(n) + "," + y.ToString(n)+"");           
*/
            sb.Append("label=\"{");
            sb.Append(umlClass.Name);
            sb.Append("|");
            foreach(UmlProperty property in umlClass.PropertyList)
            {
                sb.Append(property.ElementLabel);
                sb.Append("\\l");
            }
            sb.Append("|");
            foreach(UmlMethod method in umlClass.MethodList)
            {
                sb.Append(method.ElementLabel);
                sb.Append("\\l");
            }
            sb.Append("}\"]");
            return sb.ToString();
        }

        public static string GetGraphVizString(UmlLine line)
        {
            return line.end1.GetHashCode().ToString() + " -> " + line.end2.GetHashCode().ToString();
        }

        public static string GetGraphVizString(List<UmlControl> controls, List<UmlLine> lines)
        {
            NumberFormatInfo n = new NumberFormatInfo();
            n.NumberDecimalSeparator = ".";
            Canvas rootCanvas = (controls[0].Parent as Canvas);

            StringBuilder sb = new StringBuilder();
            sb.AppendLine("digraph G {");
            sb.AppendLine("rankdir=BT");
            sb.AppendLine("fontname=\"Segoe UI\"");
            sb.AppendLine("fontsize=8");
         //   sb.AppendLine("size=\"" + (rootCanvas.ActualWidth / 100).ToString(n) + "," + (rootCanvas.ActualHeight / 100).ToString(n) + "\"");
            sb.AppendLine("ratio=\"compress\"");
        //    sb.AppendLine("splines=\"line\"");
            sb.AppendLine("node [");
            sb.AppendLine("fontname=\"Segoe UI\"");
            sb.AppendLine("fontsize=8");
            sb.AppendLine("shape=\"record\"");
            sb.AppendLine("]");
            sb.AppendLine("edge [");
  //          sb.AppendLine("minlen=\"2\"");
            sb.AppendLine("fontname=\"Segoe UI\"");
            sb.AppendLine("fontsize=8");
       //     sb.AppendLine("arrowhead=\"none\"");
            sb.AppendLine("]");

            foreach (UmlControl control in controls)
                sb.AppendLine(GetGraphVizString(control));
            foreach (UmlLine line in lines)
                sb.AppendLine(GetGraphVizString(line));
            sb.AppendLine("}");

            return sb.ToString();
        }

        public static PathFigure CalculateArrow(PathFigure pathfig, Point pt1, Point pt2)
        {
            double ArrowLength = 15;
            double ArrowAngle = 45;
            bool IsArrowClosed = true;
            
            Matrix matx = new Matrix();
            Vector vect = pt1 - pt2;
            vect.Normalize();
            vect *= ArrowLength;

            PolyLineSegment polyseg = pathfig.Segments[0] as PolyLineSegment;
            polyseg.Points.Clear();
            matx.Rotate(ArrowAngle / 2);
//            pathfig.StartPoint = (pt2 - vect) + vect * matx;
//            polyseg.Points.Add((pt2 - vect));
            pathfig.StartPoint = pt2  + vect * matx;
            polyseg.Points.Add(pt2);

            matx.Rotate(-ArrowAngle);
            //            polyseg.Points.Add((pt2 - vect) + vect * matx);
            polyseg.Points.Add(pt2 + vect * matx);
            pathfig.IsClosed = IsArrowClosed;
            pathfig.IsFilled = true;           

            return pathfig;
        }

        public static void UpdateControls(string s, List<UmlControl> UmlControls, List<UmlLine> UmlLines)
        {
            NumberFormatInfo n = new NumberFormatInfo();
            n.NumberDecimalSeparator = ".";

            double x, y, w, h, offsety=0;
            List<Point> points = new List<Point>();

            Canvas rootCanvas = (UmlControls[0].Parent as Canvas);
 //           BSpline.SetOwnerDiagram(UmlControls[0].OwnerDiagram);

            foreach(UmlLine l in UmlLines)
                rootCanvas.Children.Remove(l.Path);
            UmlLines.Clear();

            Dictionary<int, UmlControl> classes = new Dictionary<int, UmlControl>();
            foreach (UmlControl c in UmlControls)
                classes.Add(c.GetHashCode(), c);
            string[] lines = s.Split(new char[] {'\n'});
            foreach (string line in lines)
            {
                string[] tokens = line.Split(new char[] { ' ' });
                if (tokens[0].Equals("graph"))
                {
                    double canvasSizeX = double.Parse(tokens[2], n) * 100;
                    double canvasSizeY = double.Parse(tokens[3], n) * 100;
                    offsety = canvasSizeY;
                    rootCanvas.Width = canvasSizeX;
                    rootCanvas.Height = canvasSizeY;
                } else if (tokens[0].Equals("node"))
                {
                    int id = int.Parse(tokens[1]);
                    UmlControl control = classes[id];
                    x = double.Parse(tokens[3],n)*100;
                    y = offsety - double.Parse(tokens[4], n) * 100;
                    w = double.Parse(tokens[5],n)*50;
                    h = double.Parse(tokens[6],n)*50;

                    Canvas.SetLeft(control, x-w);
                    Canvas.SetTop(control, y-h);
                    Canvas.SetRight(control, x + w);
                    Canvas.SetBottom(control, y + h);

          //          control.CalculateBounds();

                 /*   Rectangle r = new Rectangle();
                    r.Stroke = Brushes.Black;
                    r.Fill = Brushes.Blue;

                    (control.Parent as Canvas).Children.Add(r);
                    
                    Canvas.SetLeft(r, x - w);
                    Canvas.SetTop(r, y - h);
                    r.Width = w*2;
                    r.Height = h*2;*/



                }
                else if (tokens[0].Equals("edge"))
                {
                    int id1 = int.Parse(tokens[1]);
                    int id2 = int.Parse(tokens[2]);
                    int nodeCount = int.Parse(tokens[3]);

                    points.Clear();

                    for (int i = 0; i < nodeCount; i++)
                    {
                        x = double.Parse(tokens[(i*2)+4],n)*100;
                        y = offsety - double.Parse(tokens[(i * 2) + 5], n) * 100;

                        points.Add(new Point(x, y));
                    }

                    UmlLine umlLine = new UmlLine();
                    umlLine.end1 = classes[id1];
                    umlLine.end2 = classes[id2];
           /*         umlLine.end1.AddStartLink(umlLine);
                    umlLine.end2.AddEndLink(umlLine);
             */       umlLine.PathGeometry = new PathGeometry();

                    points[0] = umlLine.end1.MagnetPoint(points[0]);
                    points[points.Count - 1] = umlLine.end2.MagnetPoint(points[points.Count - 1]);

                    PathFigure pf = new PathFigure();
                    pf.StartPoint = points[0];
                    PolyBezierSegment bs = new PolyBezierSegment();
                    bs.IsSmoothJoin = true;
                    bs.IsStroked = true;
                    for (int i = 1; i < points.Count; i++)
                        bs.Points.Add(points[i]);
                    pf.Segments.Add(bs);
                    pf.IsFilled = false;
                    umlLine.PathGeometry.Figures.Add(pf);
                    PathFigure pfEnd = new PathFigure();

                    pfEnd.Segments.Add(new PolyLineSegment());

                    umlLine.PathGeometry.Figures.Add(CalculateArrow(pfEnd, points[points.Count-2], points[points.Count-1]));

                    Path path = new Path();
                    path.Stroke = Brushes.Black;
                    path.Fill = Brushes.Black;
                    path.DataContext = umlLine;
                    path.SetBinding(Path.DataProperty, "PathGeometry");
                    umlLine.Path = path;
                    UmlLines.Add(umlLine);
                    rootCanvas.Children.Add(path);

                    path.MouseLeftButtonDown += new System.Windows.Input.MouseButtonEventHandler(bspline_MouseDown);
                    path.ContextMenu = UmlLineContextMenu.GetMenu();
                }
            }
        }

        static void bspline_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
        {
            BSpline.Select(sender as Path);
        }

        public static void DoLayout(List<UmlControl> controls, List<UmlLine> lines)
        {
            string s = GetGraphVizString(controls, lines);
            s = CallGraphViz(s);
            UpdateControls(s, controls, lines);
        }
    }

    public class UmlLineContextMenu : ContextMenu
    {
        private static UmlLineContextMenu _instance;
        private void BuildMenu()
        {
            MenuItem item = new MenuItem();
            item.Header = "Straight";
            item.Click += new RoutedEventHandler(straight_Click);

            _instance.Items.Add(item);
        }

        void straight_Click(object sender, RoutedEventArgs e)
        {
            UmlLine umlLine = (sender as MenuItem).DataContext as UmlLine;
            PathFigure fg = umlLine.PathGeometry.Figures[0];
            Point start, end, temp;
            start = fg.StartPoint;
            PointCollection pc = (fg.Segments[0] as PolyBezierSegment).Points;
            end = pc[pc.Count - 1];
            Point vector = new Point((end.X - start.X) / pc.Count, (end.Y - start.Y) / pc.Count);
            temp = new Point(start.X + vector.X, start.Y + vector.Y);
            for (int i = 0; i < pc.Count - 1; i++)
            {
                pc[i] = temp;
                temp.Offset(vector.X, vector.Y);
            }

            GraphViz.CalculateArrow(umlLine.PathGeometry.Figures[1], pc[pc.Count - 2], pc[pc.Count - 1]);
            BSpline.Select(umlLine.Path);
        }

        public static UmlLineContextMenu GetMenu()
        { 
            if (_instance == null)
            {
                _instance = new UmlLineContextMenu();
                _instance.BuildMenu();
            }
            return _instance;
        }
    }

    public class BSpline
    {
        private static BSpline current = new BSpline();

        private PathFigure pf;
        private PathFigure endArrow;
        private PolyBezierSegment bs;
        private List<Thumb> controlThumbs = new List<Thumb>();
        private PointCollection _controlPoints;
        private List<Line> controlLines = new List<Line>();
        private Point _startPoint;

        private UmlLine umlLine;

        public static bool CanDrag = false;

        public UmlDiagramControl OwnerDiagram
        {
            get;
            set;
        }

        public PointCollection controlPoints
        {
            get { return _controlPoints; }
        }

        public Point startPoint
        {
            get { return _startPoint; }
        }

        private Canvas rootCanvas = null;

        public void DeselectPath()
        {
            if (rootCanvas != null)
            {
                foreach (Line line in controlLines)
                    rootCanvas.Children.Remove(line);
                foreach (Thumb thumb in controlThumbs)
                    rootCanvas.Children.Remove(thumb);
                controlLines.Clear();
                controlThumbs.Clear();
                _controlPoints = null;
                rootCanvas = null;
            }
        }

        private Thumb AddThumb(Point p)
        {
            Thumb t = new Thumb();
            t.Height = 8;
            t.Width = 8;
            Canvas.SetLeft(t, p.X - 4);
            Canvas.SetTop(t, p.Y - 4);
            rootCanvas.Children.Add(t);
            t.DragDelta += new DragDeltaEventHandler(t_DragDelta);
            t.PreviewMouseLeftButtonDown += new System.Windows.Input.MouseButtonEventHandler(t_PreviewMouseDown);
            t.PreviewMouseUp += new System.Windows.Input.MouseButtonEventHandler(t_PreviewMouseUp);
            return t;
        }

        void t_PreviewMouseUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
        {
            CanDrag = false;
 //           OwnerDiagram.EnableCanvasDrag();
        }

        void t_PreviewMouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
        {
 //           OwnerDiagram.DisableCanvasDrag();
            CanDrag = true;
        }

        void t_DragDelta(object sender, DragDeltaEventArgs e)
        {
            int count = _controlPoints.Count;

            Thumb item = sender as Thumb;
            int index = controlThumbs.IndexOf(item);
            Point p;

            if (index==0)
                p = _startPoint;
            else
                p = _controlPoints[index - 1];

            double X = p.X + e.HorizontalChange;
            double Y = p.Y + e.VerticalChange;
            
            p = new Point(X,Y);

            if (index == count)
            {
                p = umlLine.end2.MagnetPoint(p);
                X = p.X;
                Y = p.Y;
            }

            Canvas.SetLeft(item, X - 4);
            Canvas.SetTop(item, Y - 4);

            if (index == 0)
            {
                _startPoint = p;
                BindingOperations.GetBindingExpression(pf, PathFigure.StartPointProperty).UpdateTarget();
                BindingOperations.GetBindingExpression(controlLines[0], Line.X1Property).UpdateTarget();
                BindingOperations.GetBindingExpression(controlLines[0], Line.Y1Property).UpdateTarget();
            }
            else
            {
                _controlPoints[index - 1] = p;
         //       BindingOperations.GetBindingExpression(bs, PolyBezierSegment.PointsProperty).UpdateTarget();
                BindingOperations.GetBindingExpression(controlLines[index - 1], Line.X2Property).UpdateTarget();
                BindingOperations.GetBindingExpression(controlLines[index - 1], Line.Y2Property).UpdateTarget();
                if (index < count)
                {
                    BindingOperations.GetBindingExpression(controlLines[index], Line.X1Property).UpdateTarget();
                    BindingOperations.GetBindingExpression(controlLines[index], Line.Y1Property).UpdateTarget();
                }

                if (index >= count - 1)
                {
                    GraphViz.CalculateArrow(endArrow, _controlPoints[count - 2], _controlPoints[count - 1]);
                }
            }

            e.Handled = true;
        }

        private Line AddLine(Point start, Point stop, string pathStart, string pathStop)
        {
            Line l = new Line();
            l.Stroke = Brushes.Gray;
            Binding binding;

            binding = new Binding(pathStart + ".X");
            binding.Source = this;
            BindingOperations.SetBinding(l, Line.X1Property, binding);

            binding = new Binding(pathStart + ".Y");
            binding.Source = this;
            BindingOperations.SetBinding(l, Line.Y1Property, binding);

            binding = new Binding(pathStop + ".X");
            binding.Source = this;
            BindingOperations.SetBinding(l, Line.X2Property, binding);

            binding = new Binding(pathStop + ".Y");
            binding.Source = this;
            BindingOperations.SetBinding(l, Line.Y2Property, binding);

            rootCanvas.Children.Add(l);
            
            return l;
        }

        public void SelectPath(Path path)
        {
            Binding binding;

            umlLine = (UmlLine)path.DataContext;

            rootCanvas = path.Parent as Canvas;
            pf = (path.Data as PathGeometry).Figures[0];
            endArrow = (path.Data as PathGeometry).Figures[1];

            binding = new Binding("startPoint");
            binding.Source = this;
            _startPoint = pf.StartPoint;
            BindingOperations.SetBinding(pf, PathFigure.StartPointProperty, binding);
           
            bs = pf.Segments[0] as PolyBezierSegment;
            _controlPoints = bs.Points;
  
            binding = new Binding("controlPoints");
            binding.Source = this;
            BindingOperations.SetBinding(bs.Points, PolyBezierSegment.PointsProperty, binding);

            controlLines.Add(AddLine(startPoint, controlPoints[0], "startPoint", "controlPoints[0]"));

            for (int i = 0; i < controlPoints.Count-1; i++)
                controlLines.Add(AddLine(controlPoints[i], controlPoints[i + 1], "controlPoints[" + i.ToString() + "]", "controlPoints[" + (i+1).ToString() + "]"));

            controlThumbs.Add(AddThumb(startPoint));

            for (int i = 0; i < controlPoints.Count; i++)
                controlThumbs.Add(AddThumb(controlPoints[i]));
        }

        public static void Deselect()
        {
            current.DeselectPath();
        }

        public static void Select(Path path)
        {
            // TODO : rectifier l'appropriation du focus souris.
            CanDrag = true;
            current.DeselectPath();
            current.SelectPath(path);
        }

        public static void SetOwnerDiagram(UmlDiagramControl diagram)
        {
            current.OwnerDiagram = diagram;
        }
    }
}
